#include <stdlib.h>
#include "process.h"

//Tamaño del buffer de lectura de los archivos.
#define BUFFER_SIZE 1024

/**
 * Lee hasta BUFFER_SIZE bytes del archivo f y los guarda en line.
 * Devuelve la cantidad de caracteres leídos, o -1 si hubo algún error.
 * Buff debe apuntar a un string con espacio suficiente (BUFFER_SIZE)
 */
static int read_line(FILE* f, char* buff);

/**
 * Recibe números de línea y columna de la diferencia, y el archivo donde
 * debe imprimirse, e imprime la diferencia correspondiente. Se encarga
 * de las cuestiones del formato de salida, como detectar si las diferencias
 * corresponden a la línea completa.
 *
 * Para poder cerrar una linea, se debe pasar a col como -1 y en size_line
 * el numero de columna.
 * 
 * El parametro break_max se utiliza cuando no se terminan de recorrer
 * en su totalidad los archivos. Esto puede originar que lineas que son
 * totalmente diferentes, solo muestre una parte de sus diferencias.
 *
 * El parametro more_large se utiliza cuando un archivo es mas largo que
 * otro.
 */
static void print_diff(unsigned int line, unsigned int col,  
		       int size_line, FILE* output, int break_max,
		       int more_large);


int process_files(const char* from_file, const char* to_file,
		FILE* output, int max) {
	FILE* from = fopen(from_file, "r");
	FILE* to = fopen(to_file, "r");
	if (!from || !to)
		return EXIT_FAILURE;

	char buff1[BUFFER_SIZE], buff2[BUFFER_SIZE]; //datos leidos
	int n1, n2; //cantidad de caracteres leidos
	int diff_count = 0; //cantidad de diferencias
	n1 = read_line(from, buff1);
	n2 = read_line(to, buff2);

	int line = 0, col = 0; //línea y columna actuales
	int i1 = 0, i2 = 0; //cursores
	int break_max = 0;
	int more_large = 0;
	int same_at_last = 0;

	while (n1 > 0 && n2 > 0 && diff_count < max) {
		char c1 = buff1[i1++];
		char c2 = buff2[i2++];
		
		if (c1 == '\n') {
			while (c2 != '\n' && diff_count < max) {
				if (i2 >= n2) {
					n2 = read_line(to, buff2);
					i2 = 0;
				}
				++diff_count; 
				print_diff(line, col, -1, output, break_max, more_large);
				++col;
				c2 = buff2[i2++];
				if(diff_count == max)
				  break_max= 1;
			}
			print_diff(line, -1, col, output, break_max, more_large);
			++line;
			col = -1;
			same_at_last = 0;
		} else if (c2 == '\n') {
			while (c1 != '\n' && diff_count < max) {
				if (i1 >= n1) {
					n1 = read_line(from, buff1);
					i1 = 0;
				}
				++diff_count;
				print_diff(line, col, -1, output, break_max, more_large);
				++col;
				c1 = buff1[i1++];
				if(diff_count == max)
				  break_max= 1;
			}
			print_diff(line, -1, col, output, break_max, more_large);
			++line;
			col = -1;
			same_at_last = 0;
		} else if (c1 != c2) {
			++diff_count;
			print_diff(line, col, -1, output, break_max, more_large);
			if(diff_count == max) {
			  break_max= 1;
			  print_diff(line, -1, col, output, break_max, more_large);
			}
			same_at_last = 1;
		}

		++col;
		
		if (i1 >= n1) {
			n1 = read_line(from, buff1);
			i1 = 0;
		}

		if (i2 >= n2) {
			n2 = read_line(to, buff2);
			i2 = 0;
		}
	}

	//Si no alcanzamos el máximo y un archivo no terminó, el resto son
	//diferencias a mostrar (líneas completas).
	while (n1 > 0 && diff_count < max) {
		more_large = 1;
		char c = buff1[i1++];
		if (c == '\n') {
			print_diff(line, -1, col, output, break_max, more_large);
			++line;
			col = -1;
		} else {
			print_diff(line, col, -1, output, break_max, more_large);
			++diff_count;
		}
		++col;

		if (i1 >= n1) {
			n1 = read_line(from, buff1);
			i1 = 0;
		}
	}

	while (n2 > 0 && diff_count < max) {
		more_large = 1;
		char c = buff2[i2++];
		if (c == '\n') {
			print_diff(line, -1, col, output, break_max, more_large);
			++line;
			col = -1;
		} else {
			print_diff(line, col, -1, output, break_max, more_large);
			++diff_count;
		}
		++col;

		if (i2 >= n2) {
			n2 = read_line(from, buff2);
			i2 = 0;
		}
	}
    
	if(more_large || same_at_last) 
	  print_diff(line, -1, col, output, break_max, more_large);

	fclose(from);
	fclose(to);

	return EXIT_SUCCESS;
}

static int read_line(FILE* f, char* buff) {
	size_t total = fread(buff, sizeof(char), BUFFER_SIZE, f);

	//Si no leyó lo pedido verificamos el estado del stream.
	if (total != BUFFER_SIZE && ferror(f))
		return -1;

	return total;
}

void print_previous(int cols, FILE* output) {
  int i= 0;
  fprintf(output, "%i", i);  
  for (i = 1; i <= cols; ++i)
    fprintf(output, ",%i", i);
}

void close_line(int whole_line, int size_line, int last_col, 
		FILE* output, int more_large) {

  //1) Si cambio de linea, y estaba el flag de All line
  //2) Lineas vacias, en archivo mas largo
  if((whole_line && size_line == last_col) || (size_line == -1 && more_large))
    fprintf(output, "all line\n");
  //Si cambio de linea separo la salida
  else if(!whole_line)
    fprintf(output, "\n");
}

static void print_diff(unsigned int line, unsigned int col, 
		       int size_line, FILE* output, int break_max,
		       int more_large) {
	static int last_line = -1; //mantiene la última línea reportada
	static int last_col = -1; //mantiene la última columna reportada
	static int whole_line = 0; //indica si la línea completa es diferente
	
	//Cierro linea, distintos casos
	// 1) Si no hubo diferencias, ignorar.
	// 2) Si hubo diferencias, agregar el \n.
	// 3) Verificar los posibles all line, si eran falsos escribir 
	// las columnas diferencia, sino escribir all line.
	// 4) Lineas vacias, cuando los archivos son de distintos largos.
	//Por otro lado esta el flag break_max que interrumpe los que
	//eran casos de All line
	if(col == -1) {
	  if(last_line == line || (size_line == 0 && more_large)) {
	    size_line--;
	    if(size_line == -1 && more_large)
	      fprintf(output, "line: %i - Cols:", line);
	    else if((whole_line && size_line != last_col) || (break_max && whole_line))  {
	      print_previous(last_col, output);
	      whole_line = 0;
	    }
	    close_line(whole_line, size_line, last_col, output, more_large);
	  }
	  return;
	}
	
	//Imprimo encabezado
	//1) Si es la columna 0, posible All line
	//2) Caso contrario, escribo la columna
	if (last_line == -1 || col == 0 || (last_line != line && col != 0)) {
 
	  //Imprimo encabezado
	  last_line = line;
	  whole_line = 0;
	  fprintf(output, "line: %i - Cols:", line);
	  
	  if (col != 0) 
	    fprintf(output, "%i", col);
	  else
	    whole_line = 1;
	  //Si no son columna consecutivas
	  //1) Si el flag de posible All line esta desactivado, escribo columna.
	  //2) Si estaba el flag activado, falso All line.
	} else if(col - last_col != 1 || !whole_line) {
	    if(!whole_line)
	      fprintf(output, ",%i", col);
	    else {
	      print_previous(++col, output);
	      whole_line = 0;
	    }	 
	}
	
	last_col = col;
}
